﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using System.ComponentModel;

namespace FontManager
{
    public class FontFont : System.ComponentModel.INotifyPropertyChanged
    {
        #region XML variables
        private string _name = "untitled";

        private List<FontCharacter> _characters = new List<FontCharacter>();
        /// <summary>
        /// Vertical spacing
        /// </summary>
        private float _space;           
        /// <summary>
        /// how many pixels wide a space character is space between each character
        /// </summary>
        private float _kerning;

        private float _xheight;
        private float _capHeight;
        /// <summary>
        /// space for dots of the letters, the limit how hight can font go bellow base
        /// </summary>
        private float _ascenderHeight;
        /// <summary>
        /// extra space between lines
        /// </summary>
        private float _leading;   
        /// <summary>
        /// space for j,g (and similar) letters which goes down, bellow base line
        /// </summary>
        private float _descenderHeight;

        /// <summary>
        /// Information used when creating text from TTF
        /// </summary>
        private string _fontOriginName;
        /// <summary>
        /// Information used when creating text from TTF
        /// </summary>
        private bool _fontOriginBold;
        /// <summary>
        /// Information used when creating text from TTF
        /// </summary>
        private bool _fontOriginItalic;
        /// <summary>
        /// Information used when creating text from TTF
        /// </summary>
        private float _fontOriginSize;
        /// <summary>
        /// Information used when creating text from TTF
        /// </summary>
        private int _fontOriginColor;

        //additional info to help know where did font came from
        [Category("FontOriginInfo"), DescriptionAttribute("value used to generate font")]
        public string FontOriginName { get { return _fontOriginName; } set { _fontOriginName = value; NotifyPropertyChanged("FontOriginName"); } }
        [Category("FontOriginInfo"), DescriptionAttribute("value used to generate font")]
        public bool FontOriginBold { get { return _fontOriginBold; } set { _fontOriginBold = value; NotifyPropertyChanged("FontOriginBold"); } }
        [Category("FontOriginInfo"), DescriptionAttribute("value used to generate font")]
        public bool FontOriginItalic { get { return _fontOriginItalic; } set { _fontOriginItalic = value; NotifyPropertyChanged("FontOriginItalic"); } }
        [Category("FontOriginInfo"), DescriptionAttribute("value used to generate font")]
        public float FontOriginSize { get { return _fontOriginSize; } set { _fontOriginSize = value; NotifyPropertyChanged("FontOriginSize"); } }
        [Category("FontOriginInfo"), DescriptionAttribute("value used to generate font")]
        public int FontOriginColor { get { return _fontOriginColor; } set { _fontOriginColor = value; NotifyPropertyChanged("FontOriginColor"); } }
        #endregion

        #region Preview Properties
        [Category("Preview"), DescriptionAttribute("present the state if a base line is drawed in the preview window")]
        public bool LineBase { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a space lines is drawed in the preview window")]
        public bool LineSpace { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a kerning lines is drawed in the preview window")]
        public bool LineKerning { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a leading line is drawed in the preview window")]
        public bool LineLeading { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a x-height line is drawed in the preview window")]
        public bool LineXHeight { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a capital letters height line is drawed in the preview window")]
        public bool LineCapHeight { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a ascender line is drawed in the preview window")]
        public bool LineAscenderHeight { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if a descender line is drawed in the preview window")]
        public bool LineDescenderHeight { get; set; }

        private string message = "";
        [Category("Preview"), DescriptionAttribute("present the top message which is drawed in the preview window")]
        public string Message
        {
            get
            {
                return message;
            }
            set
            {
                message = value;
                if (!message.Contains(" "))
                    LineSpace = false;
            }
        }

        private string _messagetwo = "";
        /// <summary>
        /// If this property is empty the second base line is not drawed.
        /// </summary>
        [Category("Preview"), DescriptionAttribute("present the bottom (multiline) message which is drawed in the preview window")]
        public string MessageTwo
        {
            get { return _messagetwo; }
            set
            {
                _messagetwo = value;
                if (_messagetwo == "")
                {
                    LineMultiBase = false;
                }
                else
                {
                    LineMultiBase = true;
                }
                if (!message.Contains(" ") && _messagetwo.Contains(" "))
                    LineSpace = true;
            }
        }

        [Category("Preview"), DescriptionAttribute("present the LineColor of background drawed in the preview window")]
        public Color Background { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if the second extra base line (multiline) is drawed in the preview window")]
        public bool LineMultiBase { get; set; }

        [Category("Preview"), DescriptionAttribute("present the state if the character width line which is drawed in the tab preview window")]
        public bool LineWidth { get; set; }

        [Category("Preview"), DescriptionAttribute("When true is using a loaded background image")]
        public bool UseBackground { get; set; }

        [Category("Preview"), DescriptionAttribute("When true is using loaded tiles as an background")]
        public bool UseTileBackground { get; set; }
        #endregion

        #region XML variables properties
        [Category("Font XML data"), DescriptionAttribute("The _name of font which will be used as an identifier in getting this font.")]
        public string Name { get { return _name; } set { _name = value; NotifyPropertyChanged("Name"); } }

        [Category("Font XML data"), DescriptionAttribute("List of all the characters in the font (from programmer view)")]
        public List<FontCharacter> Characters { get { return _characters; } set { _characters = value; NotifyPropertyChanged("Character"); } }

        [Category("Font XML data"), DescriptionAttribute("The space width inserted when 'space' key is pressed. float value.")]
        public float Space
        {
            get { return _space; }
            set
            {
                _space = (float)Math.Round(value, 3);
                NotifyPropertyChanged("Space");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("The distance inserted between the characters (A|kerning|B). float value.")]
        public float Kerning
        {
            get { return _kerning; }
            set
            {
               _kerning= (float)Math.Round(value, 3); 
                NotifyPropertyChanged("Kerning");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("Extra space between the lines when there are more than one line. float value.")]
        public float Leading
        {
            get { return _leading; }
            set
            {
                _leading= (float)Math.Round(value, 3);
                NotifyPropertyChanged("Leading");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("The XHeight based on 'x' character. float value.")]
        public float XHeight
        {
            get { return _xheight; }
            set
            {
                _xheight= (float)Math.Round(value, 3);
                NotifyPropertyChanged("XHeight");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("Capital letters height. float value.")]
        public float CapHeight
        {
            get { return _capHeight; }
            set
            {
                _capHeight= (float)Math.Round(value, 3); 
                NotifyPropertyChanged("CapHeight");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("The height of how hight from the base line it goes up. float value.")]
        public float AscenderHeight
        {
            get { return _ascenderHeight; }
            set
            {
                _ascenderHeight= (float)Math.Round(value, 3);
                NotifyPropertyChanged("AscenderHeight");
            }
        }

        [Category("Font XML data"), DescriptionAttribute("The height of how hight from the base line it goes down. float value.")]
        public float DescenderHeight
        {
            get { return _descenderHeight; }
            set
            {
                _descenderHeight= (float)Math.Round(value, 3);
                NotifyPropertyChanged("DescenderHeight");
            }
        }
        #endregion

        #region text alignment
        /// <summary>
        /// determines how the text will be drawed in the preview window. 
        /// Which alignment will be used for X value.
        /// </summary>
        public enum textAlign
        {
            Left,
            Center,
            Right
        }
        [Category("Preview"), DescriptionAttribute("how the preview text should be align")]
        public textAlign TextAlignment { get; set; }
        #endregion

        #region Contructor
        /// <summary>
        /// Constructor guessing lines visibility, message texts, background and text alignment
        /// </summary>
        public FontFont()
        {
            Name = "Untitled";
            //Above Base
            LineXHeight = true;
            LineCapHeight = true;
            LineAscenderHeight = true;
            //Base
            LineBase = true;
            //Bellow Base
            LineLeading = true;
            LineDescenderHeight = true;
            //Vertical
            LineSpace = true;
            LineKerning = true;

            LineWidth = true;

            Message = "Upper line";
            MessageTwo = "Lo1er2line";

            LineMultiBase = true;
            Background = Color.White;
            TextAlignment = textAlign.Center;
        }
        #endregion

        #region Helper methods
        /// <summary>
        /// Might be completely unused code now since I am no longer updating XML file at runtime and
        /// am using different aproach to solve whatever I was trying to solve with this method.
        /// The reason for keeping this method is for security reasons in case I will need to use it again
        /// OR removing it will causes a lot of problems.
        /// </summary>
        /// <param _name="valueType"></param>
        private void NotifyPropertyChanged(string valueType)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(valueType));
        }

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        ///method which was used to determine which property was changed.
        ///No longer used. It was used when I wanted to update xml file at runtime.
        /// </summary>
        /// <param _name="propertyType">font parameter (i.e. Name, Space, Kerning)</param>
        //public object getChangedValue(string propertyType)
        //{
        //    switch (propertyType)
        //    {
        //        case "Name":
        //            return Name;
        //        case "Space":
        //            return _space;
        //        case "Kerning":
        //            return _kerning;
        //        case "Leading":
        //            return _leading;
        //        case "XHeight":
        //            return _xheight;
        //        case "CapHeight":
        //            return _capHeight;
        //        case "AscenderHeight":
        //            return _ascenderHeight;
        //        case "DescenderHeight":
        //            return _descenderHeight;
        //        case "FontOriginName":
        //            return FontOriginName;
        //        case "FontOriginBold":
        //            return FontOriginBold;
        //        case "FontOriginItalic":
        //            return FontOriginItalic;
        //        case "FontOriginSize":
        //            return FontOriginSize;
        //        case "FontOriginColor":
        //            return FontOriginColor;
        //        default:
        //            return null;
        //    }
        //}


        /// <summary>
        /// guesses fonts properties
        /// </summary>
        public void GuessFontValues()
        {
            try
            {
                if (GetCharacter("0x0078") != null)
                {
                    XHeight = Helper.cropImage(_characters.Find(character => character.Name.Equals("0x0078")).Image).Height;
                }
                else
                {
                    _xheight = 18;
                }

                if (GetCharacter("0x0065") != null)
                {
                    CapHeight = Helper.cropImage(_characters.Find(character => character.Name.Equals("0x0065")).Image).Height;
                }
                else
                {
                    _capHeight = 27;
                }

                //descender height will be the height of j - x;
                if (GetCharacter("0x0070") != null)
                {
                    DescenderHeight = Helper.cropImage(_characters.Find(character => character.Name.Equals("0x0070")).Image).Height - XHeight;
                }
                else
                {
                    _descenderHeight = 8;
                }

                if (GetCharacter("0x0065") != null)
                {
                    AscenderHeight = GetCharacter("0x0065").Height;
                }
                else
                {
                    _ascenderHeight = 29;
                }

                _space = 10;
                _kerning = -2;
                _leading = 2;

                FontOriginName = "unknown";
                FontOriginBold = false;
                FontOriginItalic = false;
                FontOriginSize = 0;
                FontOriginColor = Color.Black.ToArgb();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error occured in FontFont.GuessFontValues(). \n" + ex.Message);
            }
        }

        /// <summary>
        /// controlls the state of ALL lines
        /// </summary>
        /// <param _name="value">determines if all lines are drawed or not</param>
        public void drawAllLines(bool value)
        {
            try
            {
            //Above Base
            LineXHeight = value;
            LineCapHeight = value;
            LineAscenderHeight = value;
            //Base
            LineBase = value;
            LineMultiBase = value;
            //Bellow Base
            LineLeading = value;
            LineDescenderHeight = value;
            //Vertical
            LineSpace = value;
            LineKerning = value;
            //
            LineWidth = value;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error occured in FontFont.drawAllLines(). \n" + ex.Message);
            }
        }

        #endregion

        #region contains char checking
        /// <summary>
        /// helper method to check if current Characters list contains specific 'character' value
        /// </summary>
        /// <param _name="character">character value (i.e. "0x0040")</param>
        /// <returns>return true IF characters list contains character value</returns>
        public bool ContainsCharHEX(string hex)
        {
            bool value = false;

            foreach (FontCharacter c in _characters)
                if (c.Name == hex)
                    value = true;

            return value;
        }
        /// <summary>
        /// helper method to check if current Characters list contains specific character
        /// </summary>
        /// <param _name="character">character (i.e. A,B,4)</param>
        /// <returns>return true IF characters list contains character/returns>
        public bool ContainsCharCHARACTER(char character)
        {
            bool value = false;

            char c = character;
            int unicode = c;
            if (ContainsCharHEX(string.Format("0x{0:x4}", unicode)))
                value = true;

            return value;
        }
        /// <summary>
        /// helper method to check if current Characters list contains specific ascii value
        /// </summary>
        /// <param _name="character">ASCII value</param>
        /// <returns>return true IF characters list contains ascii value</returns>
        public bool ContainsCharASCII(int ascii)
        {
            bool value = false;

            if (ContainsCharHEX(string.Format("0x{0:x4}", ascii)))
                value = true;

            return value;
        }
        /// <summary>
        /// helper method to check if current Characters list contains specific URI
        /// </summary>
        /// <param _name="character">URI(link)</param>
        /// <returns>return true IF characters list contains URI</returns>
        public bool ContainsCharImgUri(string imgUri)
        {
            bool value = false;

            if(ContainsCharHEX(Path.GetFileNameWithoutExtension(imgUri)))
                value = true;

            return value;
        }
        #endregion

        #region CHARACTERacter CRUD
        public FontCharacter CreateCharacter(string image)
        {
                FontCharacter c = new FontCharacter();
                c.Image = image;
                c.GuessValues();
                return c;
        }

        /// <summary>
        /// Takes given URI. Checks if its HEX value (if yes: IF character doesn't exists adds it to the list, IF it exists calls replace character method).
        /// </summary>
        /// <param _name="imageUri">image uri</param>
        /// <param _name="askConfirmForOverride">if its already exists do you want to override it without notifications</param>
        public void AddCharacter(string imageUri,bool askConfirmForOverride)
        {
            //if character doesn't exist
            string imageHex = Path.GetFileNameWithoutExtension(imageUri);

            if (Helper.isLinkHexPng(imageUri))
            {
                if (GetCharacter(imageHex) == null)
                {
                    _characters.Add(CreateCharacter(imageUri));
                }
                //if character already exists
                else
                {
                    ReplaceCharacter(imageHex, CreateCharacter(imageUri), askConfirmForOverride);
                }
            }
            else
            {
                MessageBox.Show("You are trying to add file which is not named after 'Hex' value.");
            }
        }

        /// <summary>
        /// checks if the character starts with "0x" and if not longer than >7.
        /// If yes executes search.
        /// </summary>
        /// <param _name="character">search character value</param>
        /// <returns>returns FontCharacter object of given character from characters list</returns>
        public FontCharacter GetCharacter(string hex)
        {
            if (hex.StartsWith("0x") && hex.Length <= 7)
            {
                bool found = false;
                int index = 0;
                while (!found && index < _characters.Count)
                {
                    if (_characters[index] != null && _characters[index].Name == hex)
                    {
                        found = true;
                        return _characters[index];
                    }
                    index++;
                }
            }
            return null;
        }

        public void DeleteCharacter(string hex)
        {
            try
            {
                _characters.Remove(GetCharacter(hex));
            }
            catch (Exception ex)
            {
                MessageBox.Show("_Font.DeleteCharacter() \n" + ex.Message);
            }
        }

        /// <summary>
        /// replaces the character in the list with a new value IF that value is not the same as old one (the same URI).
        /// </summary>
        /// <param _name="character">replace character with this value in the list</param>
        /// <param _name="newChar">character which will be used instead of the old one</param>
        /// <param _name="askForConfim">if the character already exists do you wanna confirmation dialog to appear?</param>
        public void ReplaceCharacter(string hex, FontCharacter newChar, bool askForConfim)
        {
            int hexIndex = _characters.IndexOf(GetCharacter(hex));
            //if inserted image is in the same folder and with the same _name
            if (_characters[hexIndex].Image == newChar.Image)
            {
                return;
            }
            else
            {
                if (askForConfim)
                {
                    DialogResult ds = MessageBox.Show(string.Format("Override and rename {0} to {1}", hex, newChar.Name), "Override", MessageBoxButtons.YesNo);
                    if (ds == DialogResult.Yes)
                    {
                        string newUri = Path.Combine(Path.GetDirectoryName(newChar.Image), hex + ".png");

                        if(!File.Exists(newUri))
                            File.Copy(newChar.Image, newUri, true);
                        
                        newChar.Image = newUri;
                        newChar.GuessValues();
                        _characters[hexIndex] = newChar;
                    }
                }
                else
                {
                    string newUri = Path.Combine(Path.GetDirectoryName(newChar.Image), hex + ".png");

                    File.Move(newChar.Image, newUri);
                    newChar.Image = newUri;
                    newChar.GuessValues();
                    _characters[hexIndex] = newChar;
                }
            }
        }
        #endregion
        /// <summary>
        /// checks if list contains given URI (image location)
        /// </summary>
        /// <param _name="link">search this link in the list</param>
        /// <returns>search result</returns>
        public bool ContainsChar(string link)
        {
            bool found = false;
            int i = 0;
            while (!found && i < Characters.Count)
            {
                if (Characters[i].Image == link)
                {
                    found = true;
                }
                i++;
            }
            return found;
        }
    }
}
